﻿namespace Blaze.Security
{
    using System;
    using System.Globalization;
    using System.Runtime.InteropServices;

    /// <summary>
    /// 用于内存加密<see cref="double"/>型的变量。
    /// </summary>
    public struct SecureDouble : IEquatable<SecureDouble>
    {
        /// <summary>
        /// 当检测到内存作弊时调用此方法（只会触发一次，之后会被设为null）
        /// </summary>
        public static Action onCheatingDetected;

        private static long cryptoKey = 211145L;
        private long currentCryptoKey;
        private DoubleLongBytesUnion hiddenValue;
        private double fakeValue;
        private bool inited;

        private SecureDouble(DoubleLongBytesUnion value)
        {
            currentCryptoKey = cryptoKey;
            hiddenValue = value;
            fakeValue = 0;
            inited = true;
        }

        /// <summary>
        /// 设置新的加密密钥。
        /// </summary>
        /// <param name="newKey">新的密钥</param>
        public static void SetNewCryptoKey(long newKey)
        {
            cryptoKey = newKey;
        }

        /// <summary>
        /// 获取加密后的值。
        /// </summary>
        public long GetEncrypted()
        {
            if (currentCryptoKey != cryptoKey)
            {
                var temp = InternalDecrypt();
                hiddenValue = InternalEncrypt(temp);
                currentCryptoKey = cryptoKey;
            }

            var union = new DoubleLongBytesUnion
            {
                b1 = hiddenValue.b8,
                b2 = hiddenValue.b7,
                b3 = hiddenValue.b6,
                b4 = hiddenValue.b5,
                b5 = hiddenValue.b4,
                b6 = hiddenValue.b3,
                b7 = hiddenValue.b2,
                b8 = hiddenValue.b1,
            };
            return union.l;
        }

        /// <summary>
        /// 将变量设置为加密后的值。
        /// </summary>
        /// <param name="encrypted">加密后的值</param>
        public void SetEncrypted(long encrypted)
        {
            var union = new DoubleLongBytesUnion {l = encrypted};

            hiddenValue = new DoubleLongBytesUnion
            {
                b1 = union.b8,
                b2 = union.b7,
                b3 = union.b6,
                b4 = union.b5,
                b5 = union.b4,
                b6 = union.b3,
                b7 = union.b2,
                b8 = union.b1,
            };

            if (onCheatingDetected != null)
            {
                fakeValue = InternalDecrypt();
            }
        }

        /// <summary>
        /// 使用默认的密钥加密变量。
        /// </summary>
        /// <param name="value">明文</param>
        /// <returns>密文</returns>
        public static long Encrypt(double value)
        {
            return Encrypt(value, cryptoKey);
        }

        /// <summary>
        /// 使用指定的密钥加密变量。
        /// </summary>
        /// <param name="value">明文</param>
        /// <param name="key">密钥</param>
        /// <returns>密文</returns>
        public static long Encrypt(double value, long key)
        {
            var u = new DoubleLongBytesUnion {d = value};
            u.l = u.l ^ key;

            return u.l;
        }

        private static DoubleLongBytesUnion InternalEncrypt(double value, long key = 0L)
        {
            var currKey = key;
            if (currKey == 0L)
            {
                currKey = cryptoKey;
            }

            var u = new DoubleLongBytesUnion {d = value};
            var ret = new DoubleLongBytesUnion
            {
                b1 = u.b8,
                b2 = u.b7,
                b3 = u.b6,
                b4 = u.b5,
                b5 = u.b4,
                b6 = u.b3,
                b7 = u.b2,
                b8 = u.b1,
            };
            ret.l = ret.l ^ currKey;
            return ret;
        }

        /// <summary>
        /// 使用默认的密钥解密变量。
        /// </summary>
        /// <param name="value">密文</param>
        /// <returns>明文</returns>
        public static double Decrypt(long value)
        {
            return Decrypt(value, cryptoKey);
        }

        /// <summary>
        /// 使用指定的密钥解密变量。
        /// </summary>
        /// <param name="value">密文</param>
        /// <param name="key">密钥</param>
        /// <returns>明文</returns>
        public static double Decrypt(long value, long key)
        {
            var temp = new DoubleLongBytesUnion {l = value};
            temp.l = temp.l ^ key;
            var ret = new DoubleLongBytesUnion
            {
                b1 = temp.b8,
                b2 = temp.b7,
                b3 = temp.b6,
                b4 = temp.b5,
                b5 = temp.b4,
                b6 = temp.b3,
                b7 = temp.b2,
                b8 = temp.b1,
            };
            return ret.d;
        }

        private double InternalDecrypt()
        {
            if (!inited)
            {
                currentCryptoKey = cryptoKey;
                hiddenValue = InternalEncrypt(0);
                fakeValue = 0;
                inited = true;
            }

            var key = cryptoKey;

            if (currentCryptoKey != cryptoKey)
            {
                key = currentCryptoKey;
            }

            var temp = hiddenValue;
            temp.l = temp.l ^ key;
            var union = new DoubleLongBytesUnion
            {
                b1 = temp.b8,
                b2 = temp.b7,
                b3 = temp.b6,
                b4 = temp.b5,
                b5 = temp.b4,
                b6 = temp.b3,
                b7 = temp.b2,
                b8 = temp.b1,
            };

            var decrypted = union.d;

            if (onCheatingDetected != null && fakeValue != 0 && Math.Abs(decrypted - fakeValue) > 0.000001d)
            {
                onCheatingDetected();
                onCheatingDetected = null;
            }

            return decrypted;
        }

        [StructLayout(LayoutKind.Explicit)]
        private struct DoubleLongBytesUnion
        {
            [FieldOffset(0)] public double d;

            [FieldOffset(0)] public long l;

            [FieldOffset(0)] public byte b1;

            [FieldOffset(1)] public byte b2;

            [FieldOffset(2)] public byte b3;

            [FieldOffset(3)] public byte b4;

            [FieldOffset(4)] public byte b5;

            [FieldOffset(5)] public byte b6;

            [FieldOffset(6)] public byte b7;

            [FieldOffset(7)] public byte b8;
        }

        public static implicit operator SecureDouble(double value)
        {
            var x = InternalEncrypt(value);
            var y = Decrypt(x.l, cryptoKey);
            var secure = new SecureDouble(x);
            if (onCheatingDetected != null)
            {
                secure.fakeValue = value;
            }
            return secure;
        }

        public static implicit operator double(SecureDouble value)
        {
            return value.InternalDecrypt();
        }

        public static SecureDouble operator ++(SecureDouble input)
        {
            var decrypted = input.InternalDecrypt() + 1d;
            input.hiddenValue = InternalEncrypt(decrypted, input.currentCryptoKey);

            if (onCheatingDetected != null)
            {
                input.fakeValue = decrypted;
            }

            return input;
        }

        public static SecureDouble operator --(SecureDouble input)
        {
            var decrypted = input.InternalDecrypt() - 1d;
            input.hiddenValue = InternalEncrypt(decrypted, input.currentCryptoKey);

            if (onCheatingDetected != null)
            {
                input.fakeValue = decrypted;
            }
            return input;
        }

        public override string ToString()
        {
            return InternalDecrypt().ToString(CultureInfo.InvariantCulture);
        }

        public string ToString(string format)
        {
            return InternalDecrypt().ToString(format);
        }

        public string ToString(IFormatProvider provider)
        {
            return InternalDecrypt().ToString(provider);
        }

        public string ToString(string format, IFormatProvider provider)
        {
            return InternalDecrypt().ToString(format, provider);
        }

        public override bool Equals(object obj)
        {
            if (!(obj is SecureDouble))
                return false;
            var d = (SecureDouble) obj;
            var dParam = d.InternalDecrypt();
            var dThis = InternalDecrypt();

            if (dParam == dThis)
                return true;
            return double.IsNaN(dParam) && double.IsNaN(dThis);
        }

        public bool Equals(SecureDouble obj)
        {
            var dParam = obj.InternalDecrypt();
            var dThis = InternalDecrypt();

            if (dParam == dThis)
                return true;
            return double.IsNaN(dParam) && double.IsNaN(dThis);
        }

        public override int GetHashCode()
        {
            return InternalDecrypt().GetHashCode();
        }
    }
}